package runner.PSO_VM;

import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.power.PowerHost;
import runner.SAPSO_VM.log.LOG;
import runner.jswarm_pso.FitnessFunction;
import runner.jswarm_pso.Particle;
import runner.utils.Constants;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class SchedulerFitnessFunction extends FitnessFunction {
    private List<PowerHost> hostList;
    private List<Vm> vmList;
    private double[][] hostsRemainCapacity;
    private double[][] vmsRequestCapacity;

    SchedulerFitnessFunction(List<Vm> vmList, List<PowerHost> hostList) {
        super(false);
        this.vmsRequestCapacity = getVmsRequestCapacity(vmList);
        this.hostsRemainCapacity = getHostsRemainCapacity(hostList);
        this.vmList = vmList;
        this.hostList = hostList;
    }

    @Override
    public double evaluate(double[] position) {
        double[][] tempRemain = new double[hostsRemainCapacity.length][4];
        for (int i=0; i<tempRemain.length; i++) {
            for (int j=0; j<tempRemain[0].length; j++) {
                tempRemain[i][j] = hostsRemainCapacity[i][j];
            }
        }
        List<Integer> placeSet = placeVmbyPosition(tempRemain, vmsRequestCapacity, position);

        double fit = getFitnessValue(hostsRemainCapacity, tempRemain, placeSet);

//        PrintStream fitSteram = LOG.getPrintStream(output + "/" + "fitSet" + ".txt", true);
//        LOG.append(fitSteram, "fit1: %f\n", fit);
//        LOG.closeLog(fitSteram);

        return fit;
    }

    @Override
    public double evaluate(double[] position, Particle particle) {
        double[][] tempRemain = new double[hostsRemainCapacity.length][4];
        for (int i=0; i<tempRemain.length; i++) {
            for (int j=0; j<tempRemain[0].length; j++) {
                tempRemain[i][j] = hostsRemainCapacity[i][j];
            }
        }
        List<Integer> placeSet = placeVmbyPosition(tempRemain, vmsRequestCapacity, position);

        double fit = getFitnessValue(hostsRemainCapacity, tempRemain, placeSet);
        return fit;
    }

    private double getFitnessValue(double[][] originalRemain, double[][] tempRemain, List<Integer> placeSet) {

        List<PowerHost> hosts = new LinkedList<>();
        double[] U = new double[originalRemain.length];   // 物理主机j的资源利用率U
        double[] Ram = new double[originalRemain.length];   // 物理主机j的资源利用率Ram
        double[] Bw = new double[originalRemain.length];   // 物理主机j的资源利用率Bw
        double[] Disk = new double[originalRemain.length];

        int nums = placeSet.size();
        // 资源利用率均值
        double[] UAvg = new double[originalRemain.length];
        double UAvgAll = 0.0;
        // 内——负载均衡
        double[] intraBalance = new double[originalRemain.length];
        double intraBalanceAll = 0.0;
        // 间——负载均衡
        double[] interBalance = new double[originalRemain.length];
        double interBalanceAll = 0.0;

        // 所有服务器CPU、内存、带宽、磁盘利用率均值
        double UCpuAvg = 0.0, URamAvg = 0.0, UBwAvg = 0.0, UDiskAvg = 0.0;

        double energy = 0.0;
        double c = 0.7;

        //int activeHosts = placeSet.size();
        for(int j : placeSet){  // j取出的是集合中的值
//          当前主机j的利用率
//            U[j] = ((originalRemain[j][0]+originalRemain[j][1]) - (tempRemain[j][0]+tempRemain[j][1])) / (originalRemain[j][0]+originalRemain[j][1]);    // 当前主机的cpu利用率
            U[j] = (originalRemain[j][0] - tempRemain[j][0]) / originalRemain[j][0];
            Ram[j] = (originalRemain[j][1] - tempRemain[j][1]) / originalRemain[j][1];
            Bw[j] = (originalRemain[j][2] - tempRemain[j][2]) / originalRemain[j][2];
            Disk[j] = (originalRemain[j][3] - tempRemain[j][3]) / originalRemain[j][3];
//            System.out.println(U[j] + "U00000000000000000000000000000000000000000000000000000000000000");
//            System.out.println(Ram[j] + "Ram[j]8888888888888888888888888888888888888888888888888888888");

            UAvg[j] = (U[j] + Ram[j] + Bw[j] + Disk[j]) / 4;
            intraBalance[j] = Math.sqrt ((Math.pow(U[j] - UAvg[j], 2) + Math.pow(Ram[j] - UAvg[j], 2)
                    + Math.pow(Bw[j] - UAvg[j], 2) + Math.pow(Disk[j] - UAvg[j], 2) ) /4 );
            intraBalanceAll += intraBalance[j];

            UCpuAvg += U[j];
            URamAvg += Ram[j];
            UBwAvg += Bw[j];
            UDiskAvg += Disk[j];

            PowerHost host = hostList.get(j);
            hosts.add(host);
            energy += (1-U[j])*c*host.getMaxPower() + host.getMaxPower()*U[j];
        }
        // 内——负载均值
        intraBalanceAll /= nums;

        UCpuAvg /= nums;
        URamAvg /= nums;
        UBwAvg /= nums;
        UDiskAvg /= nums;
//        System.out.println(UCpuAvg + "UCpuAvg--------------------------------------------------------------");
//        System.out.println(URamAvg + "URamAvg111111111111111111111111111111111111111111111111111111111111111");
//        System.out.println(UBwAvg + "UBwAvg22222222222222222222222222222222222222222222222222222222222222222222");

        double interCpu = 0.0, interRam = 0.0, interBw = 0.0, interDisk = 0.0;
        for (int j : placeSet) {
            interCpu += Math.pow(U[j] - UCpuAvg, 2);
            interRam += Math.pow(Ram[j] - URamAvg, 2);
            interBw += Math.pow(Bw[j] - UBwAvg, 2);
            interDisk += Math.pow(Disk[j] - UDiskAvg, 2);
        }
        interCpu = Math.sqrt(interCpu / nums);
        interRam = Math.sqrt(interRam / nums);
        interBw = Math.sqrt(interBw / nums);
        interDisk = Math.sqrt(interDisk / nums);
        // 间——负载均值
        interBalanceAll = (interCpu + interRam + interBw + interDisk) / 4;

        // 返回目标函数值（适应度函数值）
        double w = 0.25;
        double fitness = w*energy/nums + w*interBalanceAll + w*intraBalanceAll + w*nums;

        /*Trace.fit1.add(energy);
        Trace.fit2.add(interBalanceAll);
        Trace.fit3.add(intraBalanceAll);
        Trace.fit4.add(nums);    */

        String output = "../output";
//        PrintStream fitnessSteram = LOG.getPrintStream(output + "/" + "fitnessSet" + ".txt", true);
        PrintStream fitnessSteram1 = LOG.getPrintStream(output + "/" + "fitnessSet1" + ".txt", true);
        LOG.append(fitnessSteram1, "energy: %f\t inter: %f\t intra: %f\t nums: %d\n", energy/nums, interBalanceAll, intraBalanceAll, nums);
//        LOG.closeLog(fitnessSteram1);

        return fitness;
    }

    public int isFit(double[] vmRequest, double[] hostRemainCapacity){
        int flag = 1;
        for (int i=0; i<vmRequest.length; i++) {
            if (vmRequest[i] > hostRemainCapacity[i]) {
                flag = -1;
                break;
            }
        }
        return flag;
    }

    private List<Integer> placeVmbyPosition(double[][] Remain, double[][] Request, double[] position) {
        List<Integer> placeSet = new ArrayList<Integer>();
        //double[] position = particle.getPosition();

        int times = 0;  // 用来存储主机id值的下标，方便remain的查找
        for (int i=0; i<position.length; i++) {
            int place = (int) position[i];

            double[] hostRemainCapacity = Remain[place];
            int flag = isFit(Request[i], hostRemainCapacity);
            if (flag != -1) {
                // 若该主机(id=place)的资源剩余量满足当前vm的资源请求量
                // 则将该主机上的资源减去请求的，表示将当前vm放置在该主机上
                Remain[place][0] -= Request[i][0];
                Remain[place][1] -= Request[i][1];
                Remain[place][2] -= Request[i][2];
                Remain[place][3] -= Request[i][3];
            } else {
                for (int j=0; j<Remain.length; j++) {
                    int g = isFit(Request[i], Remain[j]);
                    if (g != -1) {
                        place = j;
                        position[i] = place;
                        Remain[place][0] -= Request[i][0];
                        Remain[place][1] -= Request[i][1];
                        Remain[place][2] -= Request[i][2];
                        Remain[place][3] -= Request[i][3];
                        break;
                    }
                }
            }
            // 物理主机集合
            if (!placeSet.contains(place)) {
                placeSet.add(place);
            }
        }
        return placeSet;
    }

    private double sum(double[] f) {
        double d = 0;
        for (double v : f) {
            d = d + v;
        }
        return d;
    }

//    private double[] getFitnessValueOfParticle(double[][] hostsRemain,double[][] vmsRequest, SchedulerParticle[] particles) {
//        double[] f = new double[particles.length];
//        double[][] tempRemain = new double[hostsRemain.length][3];
//        for(int i=0; i<hostsRemain.length; i++){
//            for(int j=0; j<3; j++){
//                tempRemain[i][j] = hostsRemain[i][j];
//            }
//        }
//        for (int k=0; k<particles.length; k++) {
//            List<Integer> placeSet = placeVm(tempRemain, vmsRequest, particles[k]);
//            if (placeSet == null) {
//                f[k] = -1;
//            } else {
//                // 得到每一个粒子的适应度值
//                f[k] = getFitnessValue(hostsRemain, tempRemain, placeSet);
//                for(int i=0;i<hostsRemain.length;i++){
//                    for(int j=0;j<3;j++){
//                        tempRemain[i][j] = hostsRemain[i][j];
//                    }
//                }
//            }
//        }
//        return f;
//    }
    //particle是粒子有位置和速度
//    private List<Integer> placeVm(double[][] Remain, double[][] Request, SchedulerParticle particle) {
//        List<Integer> placeSet = new ArrayList<Integer>();
//        double[] position = particle.getPosition();
//        for (int i=0; i<position.length; i++) {
//            int place = (int) position[i];
////            if (isFit(Request[i], Remain[place])) {
////                // 若该主机(id=place)的资源剩余量满足当前vm的资源请求量
////                // 则将该主机上的资源减去请求的，表示将当前vm放置在该主机上
////                Remain[place][0] -= Request[i][0];
////                Remain[place][1] -= Request[i][1];
////                Remain[place][2] -= Request[i][2];
////            } else {
////                int hostIndex;
////                for (PowerHost h : hostList) {
////                    hostIndex = h.getId();
////                    if (isFit(Request[i], Remain[hostIndex])) {
////                        Remain[hostIndex][0] -= Request[i][0];
////                        Remain[hostIndex][1] -= Request[i][1];
////                        Remain[hostIndex][2] -= Request[i][2];
////                        place = hostIndex;
////                        break;
////                    }
////                }
////            }
//            if (!isFit(Request[i], Remain[place])) {
//                for (int j=0; j<Remain.length; j++) {
//                    if (isFit(Request[i], Remain[j])) {
//                        place = j;
//                        break;
//                    }
//                }
//                for (PowerHost h : hostList) {
//                    if (isFit(Request[i], Remain[h.getId()])) {
//                        place = h.getId();
//                        break;
//                    }
//                }
//            }
//            Remain[place][0] -= Request[i][0];
//            Remain[place][1] -= Request[i][1];
//            Remain[place][2] -= Request[i][2];
//            if (!placeSet.contains(place)) {
//                placeSet.add(place);
//            }
//        }
//        return placeSet;
//    }

//    public boolean isFit(double[] vmRequest, double[] hostRemainCapacity){
//        boolean flag = true;
//        for(int i=0; i<vmRequest.length; i++){
//            if(vmRequest[i] > hostRemainCapacity[i]){
//                flag = false;
//                break;
//            }
//        }
//        return flag;
//    }
//
//    private double getFitnessValue(double[][] originalRemain, double[][] tempRemain, List<Integer> placeSet) {
//        double fv=0;
//        int activeHosts = placeSet.size();
//        for(int i : placeSet){
//            fv += (originalRemain[i][0] - tempRemain[i][0])/originalRemain[i][0];
//            fv += (originalRemain[i][1] - tempRemain[i][1])/originalRemain[i][1];
//            fv += (originalRemain[i][2] - tempRemain[i][2])/originalRemain[i][2];
//        }
//        return fv/activeHosts;
//    }


    public double[][] getVmsRequestCapacity (List<? extends Vm> vmList) {
        double[][] request = new double[vmList.size()][4];
        for (int i=0; i<vmList.size(); i++) {
            Vm vm = vmList.get(i);
//            request[i][0] = vm.getCurrentRequestedMaxMips();
//            request[i][1] = vm.getCurrentRequestedRam();
//            request[i][2] = vm.getCurrentRequestedBw();
            request[i][0] = vm.getCurrentRequestedTotalMips();
            request[i][1] = vm.getCurrentRequestedRam();
            request[i][2] = vm.getCurrentRequestedBw();
            request[i][3] = vm.getCurrentRequestedDisk();
        }
        return request;
    }

    public double[][] getHostsRemainCapacity (List<PowerHost> hostList) {
        double cpuT = 1;  // 0.8 cpu利用率阈值为0.8(判断是否过载)
        double ramT = 1;
        double bwT = 1;
        double diskT = 1;

        double[][] remain = new double[hostList.size()][4];
        int hostIndex = 0;
        // CPU 内存 带宽 磁盘
        for (PowerHost h : hostList) {
//            remain[hostIndex][0] = h.getAvailableMips() * cpuT - h.getUtilizationOfCpuMips();
            remain[hostIndex][0] = h.getTotalMips() * cpuT - h.getUtilizationOfCpuMips();
            remain[hostIndex][1] = h.getRam() * ramT - h.getUtilizationOfRam();
            remain[hostIndex][2] = h.getBw() * bwT - h.getUtilizationOfBw();
            remain[hostIndex][3] = h.getDisk() * diskT - h.getUtilizationOfDisk();
            hostIndex ++;
        }
        return remain;
    }

}
